/**
 * Copyright (c) 2010 Sense Tecnic Systems Inc.
 */
package com.testcase.vincentApp;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;

/**
 * Custom view that pages displays its elements in a series of "pages" that the
 * user can "scroll" horizontally through by swiping. Similar to the iPhone
 * paging UI. Also implements "bounce back" scrolling when the edges of the view
 * have been reached.
 * 
 * Most of this code is taken from the Workspace class in the Launcher project
 * in Android:
 * 
 * http://android.git.kernel.org/?p=platform/packages/apps/Launcher.git
 * 
 * @author Vincent Tsao
 */
public class HorizontalPagingScrollView extends ViewGroup
{
	// velocity at which a fling gesture will cause a snap to the next screen
	private static final int SNAP_VELOCITY = 475;
	private static final int INVALID_SCREEN = -1;
	private static final int DEFAULT_SCREEN = 0;

	private Scroller scroller;
	private int currentScreen;
	private int nextScreen;
	private int maximumVelocity;
	private boolean firstLayout;
	private VelocityTracker velocityTracker;
	private float lastMotionX;

	/**
	 * Used to inflate the HorizontalPagingScrollView from XML.
	 * 
	 * @param context The application's context.
	 * @param attrs The attributes set containing the Workspace's customization
	 *            values.
	 */
	public HorizontalPagingScrollView (Context context, AttributeSet attrs)
	{
		super (context, attrs, 0);

		initHorizontalPagingScrollView ();
	}

	private void initHorizontalPagingScrollView ()
	{
		firstLayout = true;
		scroller = new Scroller (getContext ());
		currentScreen = DEFAULT_SCREEN;

		final ViewConfiguration configuration = ViewConfiguration.get (getContext ());
		maximumVelocity = configuration.getScaledMaximumFlingVelocity ();
	}

	@Override
	public boolean onTouchEvent (MotionEvent ev)
	{
		if (velocityTracker == null)
			velocityTracker = VelocityTracker.obtain ();

		velocityTracker.addMovement (ev);

		final int action = ev.getAction ();
		final float x = ev.getX ();

		switch (action)
		{
			case MotionEvent.ACTION_DOWN:

				// if being flinged and user touches, stop the fling. isFinished
				// will be false if being flinged
				if (!scroller.isFinished ())
					scroller.abortAnimation ();

				// remember where the motion event started
				lastMotionX = x;

				break;

			case MotionEvent.ACTION_MOVE:

				int deltaX = (int) (lastMotionX - x);
				lastMotionX = x;

				if (deltaX < 0)
				{
					if (getScrollX () > -getWidth ())
					{
						// this is to create a fake "tension" for the bounce
						// back scrolling on the left most child
						if (getScrollX () < 0)
							deltaX = deltaX / 2;

						scrollBy (Math.max (-(getScrollX () + getWidth () / 2), deltaX), 0);
					}
				}
				else if (deltaX > 0)
				{
					final int availableToScroll = getChildAt (getChildCount () - 1).getRight () - getScrollX () - getWidth () / 2;

					if (availableToScroll > 0)
					{
						// this is to create a fake "tension" for the bounce
						// back scrolling on the right most child
						if (getScrollX () > getChildAt (getChildCount () - 1).getRight () - getWidth ())
							deltaX = deltaX / 2;

						scrollBy (Math.min (availableToScroll, deltaX), 0);
					}
				}

				break;

			case MotionEvent.ACTION_UP:

				// final VelocityTracker velocityTracker = velocityTracker;
				velocityTracker.computeCurrentVelocity (1000, maximumVelocity);
				int velocityX = (int) velocityTracker.getXVelocity ();

				if (velocityX > SNAP_VELOCITY && currentScreen > 0)
				{
					// fling hard enough to move left
					snapToScreen (currentScreen - 1);
				}
				else if (velocityX < -SNAP_VELOCITY && currentScreen < getChildCount () - 1)
				{
					// fling hard enough to move right
					snapToScreen (currentScreen + 1);
				}
				else
					snapToDestination ();

				if (velocityTracker != null)
				{
					velocityTracker.recycle ();
					velocityTracker = null;
				}

				break;
		}

		return true;
	}

	@Override
	protected void onLayout (boolean changed, int l, int t, int r, int b)
	{
		int childLeft = getPaddingLeft ();

		final int count = getChildCount ();

		for (int i = 0; i < count; i++)
		{
			final View child = getChildAt (i);
			if (child.getVisibility () != View.GONE)
			{
				final int childWidth = child.getMeasuredWidth ();
				child.layout (childLeft, 0, childLeft + childWidth, child.getMeasuredHeight ());
				childLeft += childWidth;
			}
		}
	}

	@Override
	protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
	{
		super.onMeasure (widthMeasureSpec, heightMeasureSpec);

		final int width = MeasureSpec.getSize (widthMeasureSpec);
		final int widthMode = MeasureSpec.getMode (widthMeasureSpec);

		if (widthMode != MeasureSpec.EXACTLY)
			throw new IllegalStateException ("Workspace can only be used in EXACTLY mode.");

		final int heightMode = MeasureSpec.getMode (heightMeasureSpec);

		if (heightMode != MeasureSpec.EXACTLY)
			throw new IllegalStateException ("Workspace can only be used in EXACTLY mode.");

		// the children are given the same width and height as the parent
		final int count = getChildCount ();

		for (int i = 0; i < count; i++)
			getChildAt (i).measure (widthMeasureSpec, heightMeasureSpec);

		if (firstLayout)
		{
			scrollTo (currentScreen * width, 0);
			firstLayout = false;
		}
	}

	@Override
	public void computeScroll ()
	{
		if (scroller.computeScrollOffset ())
		{
			scrollTo (scroller.getCurrX (), scroller.getCurrY ());
			postInvalidate ();
		}
		else if (nextScreen != INVALID_SCREEN)
		{
			currentScreen = Math.max (0, Math.min (nextScreen, getChildCount () - 1));
			nextScreen = INVALID_SCREEN;
		}
	}

	private void snapToDestination ()
	{
		final int screenWidth = getWidth ();
		final int whichScreen = (getScrollX () + (screenWidth / 2)) / screenWidth;

		snapToScreen (whichScreen);
	}

	void snapToScreen (int whichScreen)
	{
		if (!scroller.isFinished ())
			return;

		whichScreen = Math.max (0, Math.min (whichScreen, getChildCount () - 1));

		nextScreen = whichScreen;

		final int newX = whichScreen * getWidth ();
		final int delta = newX - getScrollX ();
		scroller.startScroll (getScrollX (), 0, delta, 0, Math.abs (delta) * 2);
		invalidate ();
	}
}
